import re
import sys
import string

def changeTable(newName):
    res = ""
    res += "lua_settop(L,-2);\n"
    res += "makeTable(L, \"" + newName + "\");\n";
    return res

def addEntry(name, val):
    res = ""
    res += "setVal(L,  \"" + name + "\", " + str(val) + ");\n";
    return res

def parseSingleProto(protoLine):
    print "Parsing line:\n"
    print protoLine
    funcRe = re.compile(r"^\s*([\w:&\*]*)\s+(\w*)\s*\(([\w\s:&\*,]*)\)\s*;\s*$")
    groups = funcRe.search(protoLine).groups()
    funcRetType = groups[0]
    funcName = groups[1]
    funcArgsStr = groups[2]
    rawArgs = funcArgsStr.split(',')
    #print "Raw args length: " + str(len(rawArgs))
    argTypeList = []
    argNameList = []
    for rawArg in rawArgs:
        #print "as(" + rawArg + ")"
        tempo = rawArg.split()
        if len(tempo) == 0:
            continue
        argType = tempo[0]
        argName = tempo[1]
        argTypeList.append(argType)
        argNameList.append(argName)
    return funcRetType, funcName, argTypeList, argNameList

def produceLiProto(funcRetType, funcName, argTypeList, argNameList):
    retstr = "static int li_" + funcName + "(lua_State* L);"
    return retstr

def readArg(argType, argPos, luaPos, cdtRTab):
    retstr = ""
    retPos = luaPos
    if argType == "int" or argType == "int&":
        retstr = "int a%d = (int)lua_tointeger(L, %d);" % (argPos, luaPos)
        retPos += 1
    elif argType == "float" or argType == "float&":
        retstr = "float a%d = (float)lua_tonumber(L, %d);" % (argPos, luaPos)
        retPos += 1
    elif argType == "double" or argType == "double&":
        retstr = "double a%d = (double)lua_tonumber(L, %d);" % (argPos, luaPos)
        retPos += 1       
    elif argType == "const_char_ptr":
        retstr = "const char* a%d = lua_tostring(L, %d);" % (argPos, luaPos)
        retPos += 1
    elif argType == "bool":
        retstr = "bool a%d = (lua_toboolean(L, %d) > 0);" % (argPos, luaPos)
        retPos += 1
    elif argType == "H3DNode":
        retstr = "H3DNode a%d = unboxH3DNode(L, %d);" % (argPos, luaPos)
        retPos += 1
    elif argType == "H3DRes":
        retstr = "H3DRes a%d = unboxH3DRes(L, %d);" % (argPos, luaPos)
        retPos += 1
    elif argType == "std::string" or argType == "std::string&":
        retstr = "std::string a%d = lua_tostring(L, %d);" % (argPos, luaPos)
        retPos += 1
    elif argType == "AnyAOPtr" or argType == "AnyAOPtr&":
        retstr = "AnyAOPtr a%d = unboxAOPtr(L, %d);" % (argPos, luaPos)
        retPos += 1
    elif argType in cdtRTab:
        retstr = cdtRTab[argType] % {'argPos' : argPos, 'luaPos' : luaPos}
        retPos += 1
    else:
        retstr = "Unknown arg type: " + argType
    return retstr,retPos

def pushRet(retType, cdtPTab):
    retstr = ""
    retnum = 1
    if retType == "int":
        retstr = "lua_pushinteger(L, ret);"
    elif retType == "float" or retType == "double":
        retstr = "lua_pushnumber(L, ret);"
    elif retType == "bool":
        retstr = "lua_pushboolean(L, ret);"
    elif retType == "const_char_ptr":
        retstr = "lua_pushstring(L, ret);"
    elif retType == "H3DNode":
        retstr = "pushH3DNode(L, ret);"
    elif retType == "H3DRes":
        retstr = "pushH3DRes(L, ret);"
    elif retType == "std::string":
        retstr = "lua_pushlstring(L, ret.data(), ret.length());"
    elif retType == "AOPtr":
        retstr = "ret.pushTarget(L)"
    elif retType in cdtPTab:
        retstr = cdtPTab[retType]
    elif retType == "void":
        retstr = ""
        retnum = 0
    else:
        retstr = "Unknown ret type: " + retType
        retnum = 0
    return retstr, retnum

def createCallStr(funcRetType, funcName, numArgs, selfPrefix):
    if funcRetType == "void":
        retstr = "" + selfPrefix + funcName + "("
    else:
        retstr = funcRetType + " ret = " + selfPrefix + funcName + "("
    if numArgs == 0:
        retstr = retstr + ");"
    elif numArgs == 1:
        retstr = retstr + "a1);"
    else:
        retstr = retstr + "a1"
        for i in range(2, numArgs + 1):
            retstr = retstr + ", a" + str(i)
        retstr = retstr + ");"
    return retstr

def makeNullCheck():
    ret = ""
    ret += "\tif(op == NULL){\n"
    ret += "\t\tLOG(logERROR) << \"Null self!\";\n"
    ret += "\treturn 0;}"
    return ret

def produceLiDef(wrapperObjType, funcRetType, funcName, argTypeList, argNameList, selfPrefix, selfFormatString, startLuaPos, cdR, cdP):
    s1 = "int " + wrapperObjType + "::li_" + funcName + "(lua_State* L)\n{"
    s2 = "\t" + selfFormatString # normally something like the below 
    #s2 = "\t" + objType + "* op = (" + objType + "*)typeCheckUnboxAO(L,1,\"" + objType + "\");"
    s3 = makeNullCheck()
    argPullList = ""
    luaPos = startLuaPos # normally 2
    for i in range(1, len(argTypeList) + 1):
        tempStr, luaPos = readArg(argTypeList[i-1], i, luaPos, cdR)
        argPullList = argPullList + "\t" + tempStr + "\n"
    callStr = createCallStr(funcRetType, funcName, len(argTypeList), selfPrefix)
    pushStr, pushnum = pushRet(funcRetType, cdP)
    send = "}"
    ret = s1 + '\n' + s2 + '\n' + s3 + '\n' + argPullList + '\t' + callStr + '\n\t' + pushStr
    ret = ret + '\n' + '\treturn ' + str(pushnum) + ';\n' + send
    return ret

def produceLiDefFromCppDef(cppDef, objType, wrapperObjType, selfPrefix, selfFormatString, startLuaPos, cdR, cdP):
    funcRetType, funcName, argTypeList, argNameList = parseSingleProto(cppDef)
    return produceLiDef(objType, wrapperObjType, funcRetType, funcName, argTypeList, argNameList, selfPrefix, selfFormatString, startLuaPos, cdR, cdP)

def writeHeader(decstr, outfilename, outobjname):
    outfile = open(outfilename, "w")
    ucobjname = outobjname.upper()
    outfile.write("#ifndef " + ucobjname + "_H\n")
    outfile.write("#define " + ucobjname + "_H\n")
    outfile.write("class " + outobjname + "\n{\n")
    outfile.write("public:\n")
    outfile.write(decstr + "\n")
    outfile.write("static void pushStaticFuncs(lua_State* L);\n")
    outfile.write("};\n")
    outfile.write("#endif")
    outfile.close()

def writeCpp(namestr, defstr, outfilename, outobjname, inHeaders):
    outfile = open(outfilename, "w")
    for header in inHeaders:
        outfile.write("#include \"" + header + "\"\n")
    outfile.write("\n\nvoid " + outobjname + "::pushStaticFuncs(lua_State* L)\n{\n")
    outfile.write("\tstatic const struct luaL_Reg mylib [] = {\n")
    outfile.write(namestr)
    outfile.write("\t\t{NULL, NULL}  /* sentinel */\n\t};\n\n")
    outfile.write("\t// dump them into said table\n")
    outfile.write("\tluaL_register(L, NULL, mylib);\n")
    outfile.write("}\n\n")
    outfile.write(defstr)
    outfile.close()

def doParse(infn, wrapperType, outhfile, outcfile, indeffile):
    infile = open(infn, "r")
    alllines = infile.readlines()
    print("Read %d lines" % len(alllines))
    print("Length of first line: %d" % len(alllines[0]))

    objType = ""
    selfCallPrefix = ""
    selfGetStr = ""
    startLuaPos = 2
    decstr = ""
    defstr = ""
    namestr = ""
    cdR = dict()
    cdP = dict()

    if len(indeffile) > 0:
        parseCDFile(cdR, cdP, indeffile)

    parseNextLine = False
    parseAllLines = False
    specialName = ""

    for i in range(len(alllines) - 1):
        line = alllines[i]
        nextline = alllines[i+1]
        snextline = nextline.strip().lstrip('/').strip()
        linegrps = line.split()
        if len(linegrps) == 0:
            continue
        if linegrps[0] == "//STOP_LIAG":
            parseAllLines = False

        if (parseNextLine or parseAllLines) and (linegrps[0][0:2] != "//"):
            funcRetType, funcName, argTypeList, argNameList = parseSingleProto(line)
            lidec = produceLiProto(funcRetType, funcName, argTypeList, argNameList)
            #lidef = produceLiDef(objType, funcRetType, funcName, argTypeList, argNameList)
            lidef = produceLiDef(wrapperType, funcRetType, funcName, argTypeList, argNameList, selfCallPrefix, selfGetStr, startLuaPos, cdR, cdP)

            decstr = decstr + lidec + '\n'
            defstr = defstr + lidef + '\n\n\n'
            if specialName == "":
                upperfuncname = string.upper(funcName)[:1] + funcName[1:]
            else:
                upperfuncname = specialName
                specialName = ""
            namestr = namestr + "\t\t{\"" + upperfuncname + "\", li_" + funcName + "},\n"
            #print decstr
            #print defstr
            parseNextLine = False
            
        if linegrps[0] == "//LIAG_THIS":
            parseNextLine = True
        elif linegrps[0] == "//OBJTYPE":
            objType = linegrps[1]
            selfCallPrefix = "op->"
            selfGetStr = objType + "* op = (" + objType + "*)typeCheckUnboxAO(L,1,\"" + objType + "\");"
            startLuaPos = 2
        elif linegrps[0] == "//CALL_PREFIX":
            selfCallPrefix = snextline
        elif linegrps[0] == "//GET_SELF":
            selfGetStr = snextline
        elif linegrps[0] == "//LUA_START":
            startLuaPos = int(linegrps[1])
        elif linegrps[0] == "//PUSH_FUNC":
            cdP[linegrps[1]] = snextline
        elif linegrps[0] == "//POP_FUNC":
            cdR[linegrps[1]] = snextline
        elif linegrps[0] == "//LIAG_THESE":
            parseAllLines = True
        elif linegrps[0] == "//SPECIAL_NAME":
            specialName = linegrps[1]
    writeHeader(decstr, outhfile, wrapperType)
    writeCpp(namestr, defstr, outcfile, wrapperType, [infn, outhfile, "../core/LuaUtils.h"])

def isTextFile(fn):
    return fn[len(fn)-4:] == '.txt'
    
def isDirectory(fn):
    return fn[len(fn)-4] != '.'

def parseCDFile(cdR, cdP, fn):
    if isTextFile(fn):
        infile = open(fn, "r")
        alines = infile.readlines()
        lp = 0
        print("Parsing " + fn)
        while lp + 2 < len(alines):
            print("==> " + objType)
            objType = alines[lp].strip()
            pushFunc = alines[lp+1].strip()
            popFunc = alines[lp+2].strip()
            cdR[objType] = popFunc
            cdP[objType] = pushFunc
            lp += 3
        infile.close()
    elif isDirectory(fn):
        parseCDDir(cdR, cdP, fn)

def parseCDDir(cdR, cdP, dfn):
    allfiles = os.listdir(dfn)
    for d in allfiles:
        parseCDFile(dfn + "/" + d)

def doSMParse(infn, outfn):
    infile = open(infn, "r")
    outfile = open(outfn, "w")

    objType = ""
    decstr = ""
    defstr = ""
    namestr = ""

    parseNextLine = False
    parseAllLines = False
    
    for line in infile.readlines():
        linegrps = line.split()
        if len(linegrps) == 0:
            continue

        if linegrps[0] == "//STOP_LIAG":
            parseAllLines = False
        
        if parseNextLine or parseAllLines:
            funcRetType, funcName, argTypeList, argNameList = parseSingleProto(line)
            lidec = produceLiProto(funcRetType, funcName, argTypeList, argNameList)
            lidef = produceLiDef(objType, funcRetType, funcName, argTypeList, argNameList)
            decstr = decstr + lidec + '\n'
            defstr = defstr + lidef + '\n\n\n'
            upperfuncname = string.upper(funcName)[:1] + funcName[1:]
            namestr = namestr + "{\"" + upperfuncname + "\", li_" + funcName + "},\n"
            #print decstr
            #print defstr
            parseNextLine = False
            
        if linegrps[0] == "//LIAG_THIS":
            parseNextLine = True
        elif linegrps[0] == "//OBJTYPE":
            objType = linegrps[1]
        elif linegrps[0] == "//LIAG_THESE":
            parseAllLines = True

    outfile.write("// Paste these into the header:\n")
    outfile.write("// Automatically generated functions.\n")
    outfile.write(decstr)
    outfile.write("\n\n\n// Paste these into lua interface list:\n")
    outfile.write(namestr)
    outfile.write("\n\n\n// Paste these into the cpp:\n")
    outfile.write("// Automatically generated functions.\n")
    outfile.write(defstr)

    infile.close()
    outfile.close()

#print "Parsing " + sys.argv[1] + " to " + sys.argv[2]
#doSMParse(sys.argv[1], sys.argv[2])
#parseEnums("h3d_enums.txt", "h3d_lua_enums.txt")
#doSMParse("h3d_clean.h", "h3d_cv_res.txt")
infn = sys.argv[1]
wrapperType = sys.argv[2]
outhfile = wrapperType.lower() + ".h"
outcfile = wrapperType.lower() + ".cpp"
indeffile = ""
if len(sys.argv) >= 4:
    indeffile = sys.argv[3]
print("Parsing %s to %s and %s, using deffile %s" % (infn, outhfile, outcfile, indeffile))
doParse(infn, wrapperType, outhfile, outcfile, indeffile)
